﻿using System;
using System.Collections.Generic;
using Microsoft.Win32;
using System.Management;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;

namespace UpdateBot2.Local
{
    /// <summary>
    /// This class resembles an IO operation for extracting installed programs on the local system.
    /// Note: This class also makes use of the Win32 Registry keys.
    /// Note: No action is taken in the constructor.
    /// </summary>
    class SoftwareExplorer
    {
        /// <summary>
        /// Blank constructor.
        /// </summary>
        public SoftwareExplorer()
        {
        }

        /// <summary>
        /// Opens the Win32 registry and scans keys for list of installed software.
        /// </summary>
        /// <returns></returns>
        public List<LocalSoftwareEntry> GetLocalSoftwareList(Boolean skipBlankTitles)
        {
            List<LocalSoftwareEntry> localList = new List<LocalSoftwareEntry>(10);
            try
            {
                //Get unisntallation key
                RegistryKey uninstallKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall");
                String[] programKeys = uninstallKey.GetSubKeyNames();

                //Loop through all subkeys, extracting data.
                foreach (String pKeyName in programKeys)
                {
                    RegistryKey pKey = uninstallKey.OpenSubKey(pKeyName);

                    String title = pKey.GetValue("DisplayName", String.Empty) as String;
                    String installationDateString = pKey.GetValue("InstallDate", String.Empty) as String;   //This date isn't always avaliable.
                    DateTime installDate = parseDateTime(installationDateString);
                    String publisher = pKey.GetValue("Publisher", String.Empty) as String;
                    String versionString = pKey.GetValue("DisplayVersion", String.Empty) as String;
                    //String sizeString = pKey.GetValue("Size", String.Empty) as String;    //This is empty...unfortunately.
                    double size = -1;

                    //Check and see if version is given; otherwise, extract from name.
                    if (String.IsNullOrEmpty(versionString))
                    {
                        //No version was given; extract!
                        versionString = ExtractVersionFromString(title);
                    }
                    else
                    {
                        //Extracting version from a version may seem stupid, however, some versions
                        //Have date information within them (ie: JDiskReport). Therefore, we extract versions.
                        versionString = ExtractVersionFromString(versionString);
                    }

                    LocalSoftwareEntry localEntry = new LocalSoftwareEntry(title, new Version(versionString), publisher, installDate, size);
                    localEntry.SubKeyName = pKeyName;

                    //If skip blank titles, proceed to check for blank title.
                    if (skipBlankTitles)
                    {
                        if (!String.IsNullOrWhiteSpace(title))
                        {
                            localList.Add(localEntry);
                        }
                    }
                    else
                    {
                        //Otherwise, just add everything!
                        localList.Add(localEntry);
                    }

                    pKey.Close();
                }

                uninstallKey.Close();
                return localList;
            }
            catch (Exception e)
            {
                return localList;
            }
        }

        /// <summary>
        /// Extracts a version string from the given string.
        /// </summary>
        /// <param name="text">The raw text to examine.</param>
        /// <returns>Version string if present.</returns>
        public static String ExtractVersionFromString(String text)
        {
            Regex reg = new Regex(@"([0-9]+\.)+([0-9]+)");
            return reg.Match(text).Value;
        }

        /// <summary>
        /// Parses the date format apparently held in the registry.
        /// </summary>
        /// <param name="s"></param>
        /// <returns>DateTime.MinValue on failure.</returns>
        public static DateTime parseDateTime(String s)
        {
            if (s.Length < 8)
            {
                return DateTime.MinValue;
            }

            try
            {
                int year = int.Parse(s.Substring(0, 4));
                int month = int.Parse(s.Substring(4, 2));
                int day = int.Parse(s.Substring(6, 2));

                return new DateTime(year, month, day);
            }
            catch (Exception e)
            {
                return DateTime.MinValue;
            }
        }
    }

    /// <summary>
    /// Represents a Local installation of software.
    /// </summary>
    class LocalSoftwareEntry
    {
        public String Title { get; set; }

        /// <summary>
        /// String representation of the software number.
        /// </summary>
        public Version Version { get; set; }

        /// <summary>
        /// Publish of the software or company releasing the program.
        /// </summary>
        public String Publisher { get; set; }

        /// <summary>
        /// Date of program installation
        /// </summary>
        public DateTime InstallationDate { get; set; }

        /// <summary>
        /// Size of the installation in bytes.
        /// </summary>
        public double Size { get; set; }

        /// <summary>
        /// This is the name of the subkey in the registry. Must be manually set.
        /// </summary>
        public String SubKeyName { get; set; }

        /// <summary>
        /// Creates a LocalSoftwareEntry object.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="version"></param>
        /// <param name="publisher"></param>
        /// <param name="installationDate"></param>
        /// <param name="size"></param>
        public LocalSoftwareEntry(String title, Version version, String publisher, DateTime installationDate, double size)
        {
            this.Title = title;
            this.Version = version;
            this.Publisher = publisher;
            this.InstallationDate = installationDate;
            this.Size = size;
        }
    }
}
